﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace CoNatural {
	public static class Extensions {
		#region Type Casting

		static Dictionary<Type, Type[]> castables = new Dictionary<Type, Type[]>() {
			{ typeof(decimal),	new [] { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char) } },
			{ typeof(double),		new [] { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
			{ typeof(float),		new [] { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(long), typeof(ulong), typeof(char), typeof(float) } },
			{ typeof(ulong),		new [] { typeof(byte), typeof(ushort), typeof(uint), typeof(char) } },
			{ typeof(long),		new [] { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(int), typeof(uint), typeof(char) } },
			{ typeof(uint),		new [] { typeof(byte), typeof(ushort), typeof(char) } },
			{ typeof(int),			new [] { typeof(sbyte), typeof(byte), typeof(short), typeof(ushort), typeof(char) } },
			{ typeof(ushort),		new [] { typeof(byte), typeof(char) } },
			{ typeof(short),		new [] { typeof(byte) } }
		};

		public static bool IsCastableTo(this Type from, Type to) {
			if (to.IsNullable()) {
				to = to.GetGenericArguments()[0];
				if (from.IsNullable())
					from = from.GetGenericArguments()[0];
			}
			else if (from.IsNullable())
				return false; // cannot cast nullable to non-nullable

			if (to.IsAssignableFrom(from))
				return true;

			if (castables.ContainsKey(to) && castables[to].Contains(from))
				return true;

			return from.GetMethods(BindingFlags.Public | BindingFlags.Static).Any(m => m.ReturnType == to && (m.Name == "op_Implicit" || m.Name == "op_Explicit"));
		}

		public static bool IsNullable(this Type type) {
			return type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>));
		}

		#endregion Type Casting
	}
}
